/**
 * Copyright 2015 Hangzhou NetFrog Inc.
 *
 */
package com.m3958.encode.detector.util;


import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;

/**
 * @author jianglibo@gmail.com
 *         2015年12月4日
 *
 */
public class ReverseFileReader {
    
    private static final byte N = 0x0D;
    private static final byte R = 0x0A;
    
    private int step = 80;
    
    private int maxSteps = 100;
    
    private int doneSteps;
    
    private Path file;
    
    private byte[] partial;
    
    private List<byte[]> readedBuffers = new ArrayList<byte[]>(); 
    
    public ReverseFileReader() {
    }
    
    public ReverseFileReader(Path file) {
        this.file = file;
    }
    
    public ReverseFileReader(Path file, int step) {
        this.step = step;
    }
    
    public List<byte[]> readLines(int lineNum) {
        try {
            FileChannel fc = (FileChannel.open(file, StandardOpenOption.READ));
            long length = fc.size();
            long position = length - step;
            while (position >= 0 && doneSteps < maxSteps && readedBuffers.size() < lineNum) {
                doOneStep(fc, position, lineNum);
                position = position - step;
            }
            if (position < 0) {
                doOneStep(fc, position, lineNum);
            }
            fc.close();
            return readedBuffers;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ArrayList<byte[]>();
    }

    protected void doOneStep(FileChannel fc, long position, int lineNum) throws IOException {
        
        int trueStep = step;
        
        if (position < 0) {
            trueStep = (int)position + step;
        }
        
        ByteBuffer bytesReaded = ByteBuffer.allocate(trueStep);
        
        position = position < 0 ? 0 : position;
        fc.position(position);
        int nread;
        do {
            nread = fc.read(bytesReaded);
        } while (nread != -1 && bytesReaded.hasRemaining());
        bytesReaded.flip();
        
        if (partial != null) {
            byte[] ba = new byte[bytesReaded.limit()];
            bytesReaded.get(ba);
            ByteBuffer bb = ByteBuffer.wrap(ByteUtil.concat(ba, partial));
            bytesReaded = bb;
            partial = null;
        }
        
        int bbSize = bytesReaded.capacity();
        
        ByteBuffer bb = ByteBuffer.allocate(bbSize);
        
        List<byte[]> lines = new ArrayList<byte[]>();
        
        boolean hasPartial = true;
        
        if (position == 0) {
            hasPartial = false;
        } else {
            if (bytesReaded.limit() > 0) {
                byte startByte = bytesReaded.get();
                if (startByte == N || startByte == R) {
                    hasPartial = false;
                }
                bytesReaded.rewind();
            }
        }
        
        
        boolean nrReached = false;
        
        while(bytesReaded.hasRemaining()) {
            byte b = bytesReaded.get();
            if (b == N || b == R) { //已经读取的要取出来。
                nrReached = true;
            } else {
                if (nrReached && bb.remaining() < bbSize) { //说明有内容
                    bb.flip();
                    byte[] ba = new byte[bb.limit()];
                    bb.get(ba);
                    lines.add(ba);
                    bb.rewind();
                    bb.limit(bbSize);
                    nrReached = false;
                }
                bb.put(b);
            }
        }
        
        if (bb.remaining() < bbSize) {
            bb.flip();
            byte[] ba = new byte[bb.limit()];
            bb.get(ba);
            lines.add(ba);
            bb.rewind();
            bb.limit(bbSize);
            nrReached = false;
        }
        
        int lsize = lines.size();
        
        if (lsize > lineNum) { // 已经完成
            readedBuffers.addAll(lines.subList(lsize - lineNum, lsize));
        } else if (lsize == lineNum && !hasPartial){ //已经完成
            readedBuffers.addAll(lines);
        } else {
            if (hasPartial) { //如果有partial的话，第一行就不应该加。
                if (lsize > 0) {
                    partial = lines.get(0);
                    readedBuffers.addAll(lines.subList(1, lsize));
                }
            } else {
                readedBuffers.addAll(lines);
            }
        }
        doneSteps++;
    }
}
